package com.happe.foodeasy.dao.impl;

import com.happe.foodeasy.dao.CommonDao;
import com.happe.foodeasy.exception.ParamsAmountException;
import com.happe.foodeasy.exception.TooManyResultException;
import com.happe.foodeasy.helper.*;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

/**
 * CommonDao implementation class
 * Created by wubin on 2016-05-02.
 */
public abstract class CommonDaoImpl<T> implements CommonDao<T> {
    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcInsert insertActor;
    @Autowired
    private DaoSqlHelper sqlHelper;

    @Autowired
    public void setJdbcTemplate(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.insertActor = new SimpleJdbcInsert(jdbcTemplate);
    }

    @Override
    public void save(T saveTarget) {
        DaoHelper<T> daoHelper = new DaoHelper<>();
        jdbcTemplate.update(sqlHelper.generateInsertSqlJustColumn(daoHelper.parseTableName(getClassName()), mapKeysToArray(daoHelper.parseParams(saveTarget, getClassName()))), daoHelper.createStatementSetter());
    }

    private String[] mapKeysToArray(Map<String, Object> map) {
        String[] result = new String[map.size()];
        int i = 0;
        for (String key : map.keySet()) {
            result[i] = key;
            i++;
        }
        return result;
    }

    @Override
    public void update(T updateTarget) {

    }

    @Override
    public void delete(T deleteTarget) {

    }

    @Override
    public void selectBySql(String sql, PreparedStatementSetter setter, RowCallbackHandler handler) {
        this.jdbcTemplate.query(sql, setter, handler);
    }

    @Override
    public List<T> findManyByProperty(Object... params) {
        if (params.length % 2 != 0) {
            throw new ParamsAmountException("参数的数量应为偶数个");
        }
        DaoParamsWrapper wrapper = new DaoParamsWrapper(params);
        DaoHelper<T> daoHelper = new DaoHelper<>();
        String tableName = daoHelper.parseTableName(getClassName());
        DaoSqlHelper sqlHelper = new DaoSqlHelper();
        String sql = sqlHelper.generateSelectSqlJustColumn(tableName, wrapper.getColumnNames());
        return jdbcTemplate.query(sql, wrapper.getParams(), new RetrieveAnyEntitiesResultRowMapper<>(getClassName()));
    }

    @Override
    public T findOneByProperties(Object... params) {
        List<T> result = findManyByProperty(params);
        if (result.size() != 1) {
            throw new TooManyResultException("查出的结果不止一个");
        }
        return result.get(0);
    }

    @Override
    @SuppressWarnings("all")
    public long getSequenceNextValue(String sequenceName) {
        String sql = "select " + sequenceName + ".nextval as id from dual";
        return jdbcTemplate.queryForObject(sql, Long.class);
    }

    /**
     * sub class should implement this method to return Class object response to
     * current sub class.
     *
     * @return sub class's Class object.
     */
    public abstract Class<T> getClassName();

    private static class RetrieveAnyEntitiesResultRowMapper<M> implements RowMapper<M> {
        private Class<M> targetClass;

        private RetrieveAnyEntitiesResultRowMapper(Class<M> targetClass) {
            this.targetClass = targetClass;
        }

        @Override
        public M mapRow(ResultSet rs, int rowNum) throws SQLException {
            try {
                M t = this.targetClass.newInstance();
                Field[] fields = ClassOperateHelper.retrieveFieldsBelongProperty(this.targetClass);
                for (Field field : fields) {
                    DealRelationalAnnotationWrapper<M> dealRelationalAnnotationWrapper = new DealRelationalAnnotationWrapper<>(this.targetClass, t);
                    try {
                        if (!AnnotationHelper.checkRelationalAnnotationForField(field)) {
                            if (ClassOperateHelper.checkTargetClassIsByteArray(field.getType())) {
                                BeanUtils.setProperty(t, field.getName(), rs.getBytes(field.getName()));
                            } else if (ClassOperateHelper.checkTargetClassIsBoolean(field.getType())) {
                                String result = (String) rs.getObject(field.getName());
                                BeanUtils.setProperty(t, field.getName(), result.equals("0"));
                            } else {
                                BeanUtils.setProperty(t, field.getName(), rs.getObject(field.getName()));
                            }
                        }
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException(e);
                    }
                }
                return t;
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
